package com.android.stockme.backend.news;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import com.android.stockme.backend.configuration.IConfigurationChangedListener;
import com.android.stockme.backend.configuration.IConfigurationManager;
import com.android.stockme.backend.fetcher.INewsItem;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * 
 * @author Avishay
 *
 */
public class NewsUpdatesCenterSQLiteImpl implements INewsUpdatesCenter {

	/**
	 * @uml.property  name="configurationManager"
	 * @uml.associationEnd  multiplicity="(1 1)" inverse="newsUpdateCenter:com.android.stockme.backend.configuration.IConfigurationManager"
	 */
	private IConfigurationManager configurationManager = null;

	/**
	 * @uml.property  name="sqldb"
	 */
	private SQLiteDatabase sqldb = null;
	
	private Collection<INewsUpdateReceivedListener> listeners = null;
	
	private String timestamp = "";
	
	private static final String TABLE_NAME  = "news_tbl";
	private static final String ID_COL		= "id";
	private static final String SYMBOL_COL  = "symbol";
	private static final String PUBDATE_COL = "pubdate";
	private static final String TITLE_COL   = "title";
	private static final String LINK_COL    = "link";

	private static final String[] COLUMNS = { SYMBOL_COL, PUBDATE_COL, TITLE_COL, LINK_COL };

	/**
	 * 
	 * @param configurationManager
	 */
	public NewsUpdatesCenterSQLiteImpl(IConfigurationManager configurationManager) {
		this.configurationManager = configurationManager;
		this.listeners = new HashSet<INewsUpdateReceivedListener>();
		this.sqldb = SQLiteDatabase.create(null);

		this.sqldb.execSQL(
				"CREATE TABLE " + TABLE_NAME + " ("
                + ID_COL 		+ " INTEGER PRIMARY KEY AUTOINCREMENT,"
                + SYMBOL_COL 	+ " TEXT,"
                + PUBDATE_COL 	+ " TEXT,"
                + TITLE_COL 	+ " TEXT,"
                + LINK_COL 		+ " TEXT"
                + ");");

		
		this.configurationManager.registerStockRemovedListener(new IConfigurationChangedListener() {
			public void notifyConfigurationChanged(Object arg) {
				removeStockRelatedNews((String)arg);
			}
		});
		this.configurationManager.registerHistoryLengthChangedListener(new IConfigurationChangedListener() {
			public void notifyConfigurationChanged(Object arg) {
				evictLRU(0);
			}			
		});
	}

	/**
	 *
	 * @return
	 */
	public SortedSet<INewsItem> fetchNewsSortedByDate() {
		
		// Begin the transaction with the SQL DB
		this.sqldb.beginTransaction();
		
		try {
			SortedSet<INewsItem> items = new TreeSet<INewsItem>();
			
			// Retrieve the entire table ordered by publication date
			Cursor c = this.sqldb.query(TABLE_NAME, COLUMNS, null, null, null, null, PUBDATE_COL);

			// Create the results set
			items = this.createResultsSet(c);
			
			// Mark the transaction is successful
			this.sqldb.setTransactionSuccessful();

			return items;
		}
		finally {
			// End the transaction with the SQL DB
			this.sqldb.endTransaction();			
		}
	}

	/**
	 * 
	 * @param filter
	 * @return
	 */
	public SortedSet<INewsItem> fetchNewsSortedByFilter(String filter) {
		
//		String[] filter_array = { SYMBOL_COL, filter }; 
		
		// Begin the transaction with the SQL DB
		this.sqldb.beginTransaction();
		
		try {
			SortedSet<INewsItem> items = new TreeSet<INewsItem>();
			
			// Retrieve the entire table ordered by publication date
//			Cursor c = this.sqldb.query(TABLE_NAME, COLUMNS, "?s=\"?s\"", filter_array, null, null, PUBDATE_COL);
			Cursor c = this.sqldb.query(TABLE_NAME, COLUMNS, SYMBOL_COL + "=" + "\'" + filter + "\'", null, null, null, PUBDATE_COL);

			// Create the results set
			items = this.createResultsSet(c);
			
			// Mark the transaction is successful
			this.sqldb.setTransactionSuccessful();

			return items;
		}
		finally {
			// End the transaction with the SQL DB
			this.sqldb.endTransaction();			
		}
	}

	/**
	 *
	 * @param listener
	 */
	public void registerNewsUpdateReceivedListener(INewsUpdateReceivedListener listener) {
		if (listener != null)
			this.listeners.add(listener);
	}

	/**
	 * 
	 * @param listener
	 */
	public void unregisterNewsUpdateReceivedListener(INewsUpdateReceivedListener listener) {
		if (listener != null)
			this.listeners.remove(listener);
	}

	/**
	 * 
	 * @param items
	 */
	public void updateNewsCenter(List<INewsItem> items) {

		if (items == null)
			return;

		// Begin the transaction with the SQL DB
		this.sqldb.beginTransaction();
		
		try {

			// Make room in the DB for the news updates
			this.evictLRU(items.size());

			// Insert each News Item in a row
			ContentValues cv = new ContentValues();
			for (INewsItem item : items) {

				// Find the most up-to-date news item
				if (item.getPublishDate().compareTo(this.timestamp) > 0)
					this.timestamp = item.getPublishDate();

				cv.clear();
				cv.put(LINK_COL, item.getLink());
				cv.put(TITLE_COL, item.getTitle());
				cv.put(SYMBOL_COL, item.getSymbol());
				cv.put(PUBDATE_COL, item.getPublishDate());
				this.sqldb.insert(TABLE_NAME, SYMBOL_COL, cv);
			}

			// Mark the transaction is successful
			this.sqldb.setTransactionSuccessful();
		}
		finally {
			// End the transaction with the SQL DB
			this.sqldb.endTransaction();			
			this.postNewsUpdateReceivedEvent();
		}		
	}

	/**
	 *
	 * @return
	 */
	public String getTimestamp() {
		return this.timestamp;
	}

	@Override
	protected void finalize() throws Throwable {
		this.sqldb.close();
		super.finalize();
	}			
	
	/**
	 * 
	 */
	protected void postNewsUpdateReceivedEvent() {
		for (INewsUpdateReceivedListener listener : this.listeners)
			listener.notifyNewsUpdateReceived(null);
	}

	/**
	 * 
	 * @param symbol
	 */
	private void removeStockRelatedNews(String symbol) {
		
		String[] filter_array = { SYMBOL_COL, symbol }; 
		
		// Begin the transaction with the SQL DB
		this.sqldb.beginTransaction();
		
		try {
			// Delete all rows with symbol name corresponding to the given value
			this.sqldb.delete(TABLE_NAME, "?s=?s", filter_array);
			
			// Mark the transaction is successful
			this.sqldb.setTransactionSuccessful();
		}
		finally {
			// End the transaction with the SQL DB
			this.sqldb.endTransaction();			
		}		
	}

	/**
	 * 
	 * @param c
	 * @return
	 */
	private SortedSet<INewsItem> createResultsSet(Cursor c) {
		SortedSet<INewsItem> items = new TreeSet<INewsItem>();

		// Move the Cursor to the first row. If empty return the empty set
		if (!c.moveToFirst())
			return items;
		
		// Iterate over the retrieved results set and build the sorted set
		do {
			NewsItemImpl n = new NewsItemImpl();
			
			n.setLink(c.getString(c.getColumnIndex(LINK_COL)));
			n.setTitle(c.getString(c.getColumnIndex(TITLE_COL)));
			n.setSymbol(c.getString(c.getColumnIndex(SYMBOL_COL)));
			n.setPublishDate(c.getString(c.getColumnIndex(PUBDATE_COL)));
			
			items.add(n);
		}
		while (c.moveToNext());
		
		return items;
	}

	/**
	 * 
	 * @param updatesNum
	 */
	private void evictLRU(int updatesNum) {
		final int DB_SIZE = this.configurationManager.getNewsHistoryLength();

		// Begin the transaction with the SQL DB
		this.sqldb.beginTransaction();
		
		try {
			// Retrieve the entire table ordered by publication date
			Cursor c = this.sqldb.query(TABLE_NAME, COLUMNS, null, null, null, null, PUBDATE_COL, null);
			int rowsCount = c.getCount();
			
			// Move the Cursor to the last row
			if (!c.moveToLast())
				return;

			// Remove items from the DB until there is enough room for the new items
			while (DB_SIZE < (rowsCount + updatesNum)) {

				// Arguments for the query string
				String[] args = { LINK_COL,    c.getString(c.getColumnIndex(LINK_COL)),
								  TITLE_COL,   c.getString(c.getColumnIndex(TITLE_COL)),
								  SYMBOL_COL,  c.getString(c.getColumnIndex(SYMBOL_COL)),
								  PUBDATE_COL, c.getString(c.getColumnIndex(PUBDATE_COL)) };
				
				this.sqldb.delete(TABLE_NAME, "?s=?s and ?s=?s and ?s=?s and ?s=?s", args);
				
				rowsCount--;
				c.moveToPrevious();
			}
			
			// Mark the transaction is successful
			this.sqldb.setTransactionSuccessful();
		}
		finally {
			// End the transaction with the SQL DB
			this.sqldb.endTransaction();			
		}
	}
	
	/**
	 * 
	 * @author Avishay
	 *
	 */
	private class NewsItemImpl implements INewsItem, Comparable<INewsItem> {

		private String link        = null;
		private String title       = null;
		private String symbol      = null;
		private String publishDate = null;

		public NewsItemImpl() {
			// Empty constructor
		}
				
		public NewsItemImpl(String link, String title, String symbol, String publishDate) {
			this.link = link;
			this.title = title;
			this.symbol = symbol;
			this.publishDate = publishDate;
		}

		public String getLink() {
			return this.link;
		}

		public String getPublishDate() {
			return this.publishDate;
		}

		public String getSymbol() {
			return this.symbol;
		}

		public String getTitle() {
			return this.title;
		}

		public void setLink(String link) {
			this.link = link;
		}

		public void setTitle(String title) {
			this.title = title;
		}

		public void setSymbol(String symbol) {
			this.symbol = symbol;
		}

		public void setPublishDate(String publishDate) {
			this.publishDate = publishDate;
		}

		public int compareTo(INewsItem another) {
			if (another == null)
				return 1;

			// If objects have the same symbol, title and publication date then they are the same
			if (this.symbol.compareTo(another.getSymbol())           == 0 &&
				this.title.compareTo(another.getTitle())             == 0 &&
				this.publishDate.compareTo(another.getPublishDate()) == 0)
				return 0;

			// Otherwise sort by publication date
			return this.publishDate.compareTo(another.getPublishDate());
		}
		
		@Override
		public INewsItem clone() throws CloneNotSupportedException {
			return new NewsItemImpl(this.link, this.title, this.symbol, this.publishDate);
		}
	}
}
